package cn.schoolwow.quickserver.handler;

import cn.schoolwow.quickserver.domain.Client;
import cn.schoolwow.quickserver.domain.HttpRequestMeta;
import cn.schoolwow.quickserver.domain.MultipartFile;
import cn.schoolwow.quickserver.exception.HttpStatusException;
import cn.schoolwow.quickserver.request.ChunkedInputStream;
import cn.schoolwow.quickserver.request.FixedLengthInputStream;
import cn.schoolwow.quickserver.response.HttpStatus;
import cn.schoolwow.quickserver.util.RegExpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

/**
 * HttpRequest处理器
 */
public class HttpRequestHandler implements Handler {
    /**
     * 缓存输入
     */
    private static final ThreadLocal<ByteArrayOutputStream> threadLocal = new ThreadLocal<>();
    private static Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);
    private static String[] ipHeaders = new String[]{"X-Real-IP", "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};

    /**
     * 读取一行
     *
     * @param inputStream 输入流
     * @param charset     编码格式
     * @return 读入内容, 为null表示流读取完毕
     */
    public static String readLine(InputStream inputStream, String charset) throws IOException {
        ByteArrayOutputStream baos = threadLocal.get();
        if (null == baos) {
            baos = new ByteArrayOutputStream();
            threadLocal.set(baos);
        }
        baos.reset();
        int b;
        while ((b = inputStream.read()) != -1) {
            if (b == 0x0D) {
                b = inputStream.read();
                if (b == 0x0A) {
                    break;
                } else {
                    baos.write(0x0D);
                    baos.write(b);
                }
            } else {
                baos.write(b);
            }
        }
        if (b == -1) {
            inputStream.close();
            return null;
        }
        baos.flush();
        return new String(baos.toByteArray(), charset);
    }

    @Override
    public Handler handle(Client client) throws IOException {
        //解析请求行
        handleStatusLine(client);
        //解析http头部
        handleHeader(client);
        //提取编码格式
        getCharset(client.httpRequestMeta);
        //处理编码传输
        handleEncoding(client);
        if(client.httpRequestMeta.contentLength>0||client.httpRequestMeta.headers.containsKey("Transfer-Encoding")){
            //处理Expect请求
            handleExpect(client);
            //处理请求体
            handleContentType(client);
            //获取请求体作为字节数组
            getBodyAsBytes(client.httpRequestMeta);
        }else{
            logger.trace("[客户端无请求体]");
        }
        return new HttpSessionHandler();
    }

    /**
     * 处理请求行
     */
    private void handleStatusLine(Client client) throws IOException {
        logger.trace("[准备解析请求行]");
        String line = readLine(client.httpRequestMeta.inputStream, client.httpRequestMeta.charset);
        logger.trace("[解析请求行]{}", line);
        client.httpRequestMeta.statusLine = line;
        if (null == line||!line.contains(" ")) {
            throw new HttpStatusException(HttpStatus.BAD_REQUEST);
        }
        client.httpRequestMeta.method = line.substring(0, line.indexOf(" "));
        client.httpRequestMeta.uri = URI.create(line.substring(client.httpRequestMeta.method.length() + 1, line.lastIndexOf(" ")));
        client.httpRequestMeta.protocol = line.substring(line.lastIndexOf(" ") + 1);

        //解析路径请求参数
        String rawQuery = client.httpRequestMeta.uri.getRawQuery();
        if (null != rawQuery && !rawQuery.isEmpty()) {
            StringTokenizer st = new StringTokenizer(rawQuery, "&");
            while (st.hasMoreTokens()) {
                String token = st.nextToken();
                if(token.contains("=")){
                    String key = token.substring(0,token.indexOf("="));
                    String value = token.endsWith("=")?"":token.substring(token.indexOf("=")+1);
                    client.httpRequestMeta.dataMap.put(key, URLDecoder.decode(value, client.httpRequestMeta.charset));
                }
            }
        }
    }

    /**
     * 解析http头部
     */
    private void handleHeader(Client client) throws IOException {
        String line = readLine(client.httpRequestMeta.inputStream, client.httpRequestMeta.charset);
        while (null != line && !line.isEmpty()) {
            logger.trace("[解析Header]{}", line);
            String key = line.substring(0, line.indexOf(":"));
            String value = line.substring(key.length() + 2);
            value = new String(value.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            if (!client.httpRequestMeta.headers.containsKey(key)) {
                client.httpRequestMeta.headers.put(key, new ArrayList<>());
            }
            client.httpRequestMeta.headers.get(key).add(value);
            line = readLine(client.httpRequestMeta.inputStream, client.httpRequestMeta.charset);
        }
        //处理Cookie头部
        if (client.httpRequestMeta.headers.containsKey("Cookie")) {
            List<String> cookieList = client.httpRequestMeta.headers.get("Cookie");
            for (String cookie : cookieList) {
                StringTokenizer st = new StringTokenizer(cookie, ";");
                while (st.hasMoreTokens()) {
                    client.httpRequestMeta.cookieList.addAll(HttpCookie.parse(st.nextToken()));
                }
            }
        }
        //处理ContentLength和ContentType头部
        if (client.httpRequestMeta.headers.containsKey("Content-Length")) {
            client.httpRequestMeta.contentLength = Integer.parseInt(client.httpRequestMeta.headers.get("Content-Length").get(0));
            logger.trace("[提取ContentLength]ContentLength:{}", client.httpRequestMeta.contentLength);
        }
        if (client.httpRequestMeta.headers.containsKey("Content-Type")) {
            client.httpRequestMeta.contentType = client.httpRequestMeta.headers.get("Content-Type").get(0);
            logger.trace("[提取ContentType]ContentType:{}", client.httpRequestMeta.contentType);
        }
        //获取ip地址
        Set<String> keySet = client.httpRequestMeta.headers.keySet();
        for (String key : ipHeaders) {
            if (keySet.contains(key)) {
                client.httpRequestMeta.ip = client.httpRequestMeta.headers.get(key).get(0);
                if (client.httpRequestMeta.ip.contains(",")) {
                    client.httpRequestMeta.ip = client.httpRequestMeta.ip.substring(0, client.httpRequestMeta.ip.indexOf(","));
                }
                break;
            }
        }
        if (null == client.httpRequestMeta.ip) {
            client.httpRequestMeta.ip = client.httpRequestMeta.remoteAddress.getHostAddress();
        }
        logger.trace("[提取ip地址]{}", client.httpRequestMeta.ip);
    }

    /**
     * 从meta标签获取编码信息
     */
    private void getCharset(HttpRequestMeta httpRequestMeta) throws IOException {
        logger.trace("[准备提取编码格式]");
        getCharsetFromContentType(httpRequestMeta);
        if (null == httpRequestMeta.charset) {
            byte[] bytes = new byte[Math.min(httpRequestMeta.inputStream.available(), 1024 * 5)];
            httpRequestMeta.inputStream.mark(bytes.length);
            httpRequestMeta.inputStream.read(bytes, 0, bytes.length);
            httpRequestMeta.inputStream.reset();
            ByteBuffer firstBytes = ByteBuffer.wrap(bytes);
            getCharsetFromBOM(httpRequestMeta, firstBytes);
        }
        if (null == httpRequestMeta.charset) {
            httpRequestMeta.charset = "utf-8";
            logger.trace("[使用默认utf-8编码格式]");
        }
        logger.trace("[编码格式]{}", httpRequestMeta.charset);
    }

    /**
     * 从BOM头获取编码信息
     */
    private void getCharsetFromBOM(HttpRequestMeta httpRequestMeta, ByteBuffer byteBuffer) {
        final Buffer buffer = byteBuffer;
        buffer.mark();
        byte[] bom = new byte[4];
        if (byteBuffer.remaining() >= bom.length) {
            byteBuffer.get(bom);
            buffer.rewind();
        }
        if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF ||
                bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) {
            httpRequestMeta.charset = "utf-32";
        } else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF ||
                bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) {
            httpRequestMeta.charset = "utf-16";
        } else if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) {
            httpRequestMeta.charset = "utf-8";
        }
    }

    /**
     * 从Content-Type头部获取编码信息
     */
    private void getCharsetFromContentType(HttpRequestMeta httpRequestMeta) {
        String prefix = "charset=";
        if (null != httpRequestMeta.contentType && httpRequestMeta.contentType.contains(prefix)) {
            int startIndex = httpRequestMeta.contentType.indexOf(prefix);
            int endIndex = httpRequestMeta.contentType.lastIndexOf(";");
            if (endIndex > startIndex) {
                httpRequestMeta.charset = httpRequestMeta.contentType.substring(startIndex + prefix.length(), endIndex).trim();
            } else if (endIndex < startIndex) {
                httpRequestMeta.charset = httpRequestMeta.contentType.substring(startIndex + prefix.length()).trim();
            }
        }
    }

    /**
     * 处理编码头部
     */
    private void handleEncoding(Client client) throws IOException {
        if(client.httpRequestMeta.headers.containsKey("Transfer-Encoding")
                &&client.httpRequestMeta.contentLength>0){
            throw new IllegalArgumentException("客户端头部同时包含Content-Type字段和Transfer-Encoding,无法处理!");
        }
        //支持分块传输
        if (client.httpRequestMeta.headers.containsKey("Transfer-Encoding")) {
            logger.trace("[客户端开启了分块传输]");
            client.httpRequestMeta.inputStream = new ChunkedInputStream(client.httpRequestMeta.inputStream);
        }
        if(client.httpRequestMeta.contentLength>0){
            client.httpRequestMeta.inputStream = new FixedLengthInputStream(client.httpRequestMeta.inputStream,client.httpRequestMeta.contentLength);
        }
        //支持gzip压缩
        if (client.httpRequestMeta.headers.containsKey("Content-Encoding")) {
            String contentEncoding = client.httpRequestMeta.headers.get("Content-Encoding").get(0);
            if ("gzip".equalsIgnoreCase(contentEncoding)) {
                logger.trace("[客户端开启了gzip压缩]");
                client.httpRequestMeta.inputStream = new GZIPInputStream(client.httpRequestMeta.inputStream);
            } else if ("deflate".equalsIgnoreCase(contentEncoding)) {
                logger.trace("[客户端开启了deflate压缩]");
                client.httpRequestMeta.inputStream = new InflaterInputStream(client.httpRequestMeta.inputStream, new Inflater(true));
            }
        }
        client.httpRequestMeta.inputStream = new BufferedInputStream(client.httpRequestMeta.inputStream);
    }

    private void handleExpect(Client client) throws IOException{
        if(client.httpRequestMeta.headers.containsKey("Expect")){
            logger.trace("[处理Expect头部]返回100-Continue");
            byte[] bytes = "HTTP/1.1 100 Continue\r\nContent-Length: 0\r\n\r\n".getBytes(StandardCharsets.UTF_8);
            client.httpResponseMeta.outputStream.write(bytes);
            client.httpResponseMeta.outputStream.flush();
        }
    }

    /**
     * 根据ContentType处理内容
     */
    private void handleContentType(Client client) throws IOException {
        if(null==client.httpRequestMeta.contentType){
            return;
        }
        String contentType = client.httpRequestMeta.contentType.toLowerCase();
        if (contentType.contains("multipart/form-data")) {
            //Multipart
            getMultipartData(client.httpRequestMeta);
        }else{
            getBodyAsString(client.httpRequestMeta);
            if (contentType.contains("application/x-www-form-urlencoded")) {
                StringTokenizer st = new StringTokenizer(client.httpRequestMeta.body, "&");
                while (st.hasMoreTokens()) {
                    String token = st.nextToken();
                    client.httpRequestMeta.dataMap.put(token.substring(0, token.indexOf("=")), URLDecoder.decode(token.substring(token.indexOf("=") + 1), client.httpRequestMeta.charset));
                }
            }
        }
    }

    /**
     * 提取请求体内容
     */
    private void getBodyAsBytes(HttpRequestMeta httpRequestMeta) throws IOException {
        if(null!=httpRequestMeta.bodyBytes){
            return;
        }
        logger.trace("[读取请求体到字节数组]");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[8192];
        int length = 0;
        while((length = httpRequestMeta.inputStream.read(buffer,0,buffer.length))!=-1){
            baos.write(buffer,0,length);
        }
        httpRequestMeta.bodyBytes = baos.toByteArray();
        logger.trace("[读取请求体]长度:{},实际读取长度:{}", httpRequestMeta.contentLength, baos.size());
        baos.close();
    }

    /**
     * 提取请求体内容
     */
    private void getBodyAsString(HttpRequestMeta httpRequestMeta) throws IOException {
        if (null != httpRequestMeta.body && !httpRequestMeta.body.isEmpty()) {
            return;
        }
        logger.trace("[读取请求体作为字符串]");
        getBodyAsBytes(httpRequestMeta);
        if(null!=httpRequestMeta.bodyBytes){
            httpRequestMeta.body = new String(httpRequestMeta.bodyBytes, httpRequestMeta.charset);
        }
    }

    /**
     * 提取MultipartData信息
     */
    private void getMultipartData(HttpRequestMeta httpRequestMeta) throws IOException {
        logger.trace("[提取multipart内容]");
        httpRequestMeta.boundary = httpRequestMeta.contentType.substring(httpRequestMeta.contentType.indexOf("boundary=") + "boundary=".length());
        if (httpRequestMeta.boundary.contains(";")) {
            httpRequestMeta.boundary = httpRequestMeta.boundary.substring(0, httpRequestMeta.boundary.indexOf(";"));
        }
        byte[] boundaryBytes = ("\r\n--" + httpRequestMeta.boundary).getBytes();
        //读取第一行
        String line = readLine(httpRequestMeta.inputStream, httpRequestMeta.charset);
        while (line.equals("--" + httpRequestMeta.boundary)) {
            MultipartFile multipartFile = new MultipartFile();
            //文件属性
            {
                String contentDisposition = readLine(httpRequestMeta.inputStream, httpRequestMeta.charset);
                multipartFile.name = RegExpUtil.plainMatch(contentDisposition, "name=\"()\"");
                if (null == multipartFile.name) {
                    multipartFile.name = RegExpUtil.plainMatch(contentDisposition, "name=();");
                }
                if (contentDisposition.contains("filename=")) {
                    multipartFile.originalFilename = RegExpUtil.plainMatch(contentDisposition, "filename=\"()\"");
                    if (multipartFile.originalFilename.contains(".")) {
                        multipartFile.suffixFileName = multipartFile.originalFilename.substring(multipartFile.originalFilename.lastIndexOf(".") + 1);
                    }
                }
            }
            //额外行
            while (!line.isEmpty()) {
                if (line.startsWith("Content-Type")) {
                    multipartFile.contentType = line.substring(line.indexOf(":") + 1).trim();
                }
                line = readLine(httpRequestMeta.inputStream, httpRequestMeta.charset);
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            byte[] bytes = new byte[boundaryBytes.length];
            while ((b = httpRequestMeta.inputStream.read()) != -1) {
                if (b == 0x0D) {
                    httpRequestMeta.inputStream.mark(httpRequestMeta.boundary.length());
                    bytes[0] = 0x0D;
                    httpRequestMeta.inputStream.read(bytes, 1, bytes.length - 1);
                    if (Arrays.equals(boundaryBytes, bytes)) {
                        line = "--" + httpRequestMeta.boundary;
                        break;
                    } else {
                        baos.write(b);
                        httpRequestMeta.inputStream.reset();
                    }
                } else {
                    baos.write(b);
                }
            }

            baos.flush();
            multipartFile.bytes = baos.toByteArray();

            if (multipartFile.originalFilename == null) {
                String value = new String(multipartFile.bytes, httpRequestMeta.charset);
                httpRequestMeta.dataMap.put(multipartFile.name, value);
            } else {
                multipartFile.size = multipartFile.bytes.length;
                multipartFile.isEmpty = multipartFile.bytes.length == 0;
                List<MultipartFile> multipartFileList = httpRequestMeta.fileParameterMap.get(multipartFile.name);
                if (null == multipartFileList) {
                    multipartFileList = new ArrayList<MultipartFile>();
                }
                multipartFileList.add(multipartFile);
                httpRequestMeta.fileParameterMap.put(multipartFile.name, multipartFileList);
            }
            //--或者\r\n
            if (httpRequestMeta.inputStream.read() == 45 && httpRequestMeta.inputStream.read() == 45) {
                break;
            }
        }
    }
}